import numpy as np
import matplotlib.pyplot as plt
import Core.Individual as Individual


def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-1 * x))


def relu(x):
    result = x
    result[x < 0] = 0

    return result


# 定义适应度函数(损失函数)
def fitness(weights_mat, data_inputs, data_outputs):
    # 将种群中个体的'适应度'存入accuracy矩阵中
    accuracy = np.empty(shape=weights_mat.shape[0])

    for i in range(weights_mat.shape[0]):
        cur_mat = weights_mat[i, :]
        accuracy[i] = cal_accuracy(cur_mat, data_inputs, data_outputs)

    return accuracy


# 定义计算种群中每一个个体的'准确度'(适应度分数)
def cal_accuracy(weights_mat, data_inputs, data_outputs):
    # predictions数组记录输入data_inputs.shape[0]组数据中
    predictions = np.zeros(shape=(data_inputs.shape[0]))
    for sample_i in range(data_inputs.shape[0]):
        row = data_inputs[sample_i, :]
        # 数据集中的第一行数据与权值矩阵相乘
        for curr_weights in weights_mat:
            row = np.matmul(row, curr_weights[0])
            row = relu(row)
            row = np.matmul(row, curr_weights[1])
            row = sigmoid(row)
        # 第i个样本模型输出值
        predictions[sample_i] = row
    # 其中正确的预测值
    correct_predictions = np.where(predictions == data_outputs)[0].size
    # 返回当前个体的'分数'(适应度)
    return correct_predictions / data_outputs.size


# 初始化权值矩阵
'''
1.网络共有两个参数矩阵(一个隐层的输入和输出)
2.隐层共有56个神经元, 数据集输入共有95个数值, 故参数共有(95*56)+(56*1)
(若设置种群数量为8, 则总参数为8*[(96*8)+(56*1)])
3.随机初始化两个权值矩阵
'''


def init_matrix(n, data_inputs):

    initial_pop_weights = []
    for i in np.arange(0, n):
        HL1_neurons = 56
        input_HL1_weights = np.random.uniform(low=-0.5, high=0.5,

                                              size=(data_inputs.shape[1], HL1_neurons))

        HL2_neurons = 1
        HL1_output_weights = np.random.uniform(low=-0.5, high=0.5,

                                               size=(HL1_neurons, HL2_neurons))
        # 此处是"二维"的 -> [[][]]
        initial_pop_weights.append(np.array([input_HL1_weights,
                                             HL1_output_weights], dtype=object))
    # 将8个个体合为一个array
    initial_pop_weights = np.array(initial_pop_weights)

    return initial_pop_weights


# 定义由矩阵转一维向量的函数
'''
    由于GA需要所谓'编码操作'才能得到较好的实际表现(即将原始参数
空间的解转换为遗传算法可以操作的解空间), 此处将本BP-NN的两个权值
矩阵'拉直'为一个一维向量, 本算法称此操作为编码操作.
    将总体中所有解的参数从矩阵转换为向量
'''


def encode(mat_pop_weights):
    pop_weights_vector = []
    # 第一'行', input_HL1_weights先'装入向量数组'(按顺序)
    # 共两'行'
    for sol_idx in range(mat_pop_weights.shape[0]):

        curr_vector = []
        for layer_idx in range(mat_pop_weights.shape[1]):
            vector_weights = np.reshape(mat_pop_weights[sol_idx, layer_idx],
                                        newshape=mat_pop_weights[sol_idx, layer_idx].size)
            # 拓展向量数组(某个个体的两个参数矩阵拼接)
            curr_vector.extend(vector_weights)
        # 添加当前个体(已被转为一维向量)到数组
        pop_weights_vector.append(curr_vector)

    return np.array(pop_weights_vector)


# 定义由一维向量转矩阵的函数
'''
    解码操作, 将向量映射回权值矩阵
    (传入mat_pop_weights是为了方便知道矩阵的大小)
'''


def decode(vector_pop_weights, mat_pop_weights):
    mat_weights = []

    for sol_idx in range(mat_pop_weights.shape[0]):
        # 第一个'权值矩阵'的索引从0开始
        start = 0
        end = 0

        for layer_idx in range(mat_pop_weights.shape[1]):
            # [0,mat_weight1]
            # [mat_weight1,mat_weight2]
            end = end + mat_pop_weights[sol_idx, layer_idx].size

            curr_vector = vector_pop_weights[sol_idx, start:end]

            mat_layer_weights = np.reshape(curr_vector, newshape=mat_pop_weights[sol_idx, layer_idx].shape)
            mat_weights.append(mat_layer_weights)
            start = end

    return np.reshape(mat_weights, newshape=mat_pop_weights.shape)


# 选择操作(轮盘赌)
# p_probability储存概率
# probability_sum存储概率分布(和)
# p_new为选择完的新数组
def choice(p_gene, accuracy):
    p_choice = []
    p_probability = []
    probability_sum = []
    # 求所有个体适应度的和
    p_sum = sum(accuracy)
    # 将概率值写入p_probability数组
    for i in range(len(p_gene)):
        p_probability.append(accuracy[i] / p_sum)
    # 求出概率分布
    for i in range(len(p_gene)):
        if i == 0:
            probability_sum.append(p_probability[i])
        else:
            probability_sum.append(probability_sum[i - 1] + p_probability[i])
    # 开始选择操作
    for i in range(len(p_gene)):
        # 生成一个(0,1)的随机浮点数
        rand = np.random.uniform(0, 1)
        # 将这个浮点数和"概率分布"逐一比对,检查落在哪一块
        for j in range(len(p_gene)):
            if j == 0:
                if rand < probability_sum[j]:
                    p_choice.append(p_gene[j])
                    break
            else:
                if (rand > probability_sum[j - 1]) and (rand < probability_sum[j]):
                    p_choice.append(p_gene[j])
                    break
    # for i in range(len(p_choice)):
    #     print(p_choice[i].x)
    return p_choice


# 交叉操作(内部交叉操作使用一个交叉函数实现)
# 选择后的种群 -> 设置交叉点 -> 交叉率
# 设置交叉基因序列的后半段
# p_cross数组为交叉完得到的新种群
def cross(p_choice, cross_point, cross_rate):
    p_cross = []

    # 假设交叉完种群数量不变
    for i in range(len(p_choice)):
        # 生成一个(0,1)随机数, 模拟交叉率
        rand = np.random.uniform(0, 1)
        if rand <= cross_rate:
            # 生成两个(0,n)的随机整数, 模拟随机挑选出两个个体进行基因重组
            obj_1 = p_choice[np.random.randint(0, len(p_choice))]
            obj_2 = p_choice[np.random.randint(0, len(p_choice))]
            obj_1 = cross_function(obj_1, obj_2, cross_point)
            p_cross.append(obj_1)
            # p_cross.append(obj_2)
        # 否则当前个体不交叉(基因重组)
        else:
            p_cross.append(p_choice[i])
    # for i in range(len(p_cross)):
    #     print(p_cross[i].x)
    return p_cross


# 交叉函数
# 将第二个个体cross_point后的基因与第一个个体交换, 返回第一个个体
def cross_function(object_1, object_2, cross_point):
    # 注(join方式是以指定方式"加入" -->Python join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。)
    # cross_res = object_1.x[:cross_point] + object_2.x[cross_point:]
    object_1[:cross_point] = object_2[:cross_point]

    # 二进制码以字符串存储(不可变序列)
    # temp = object_1.x[cross_point:]
    # object_1.x[cross_point:] = object_2.x[cross_point:]
    # object_2.x[cross_point:] = temp
    return object_1


# 变异操作
def mutation(p_cross, mutant_rate):
    # 首先判断在当前变异率下是否回发生变异
    mutation_num = ((96*8)+(56*1)) * len(p_cross) * mutant_rate
    if mutation_num >= 1:
        # 将变异的基因数取整(这里使用向下取整)
        mutation_num = int(mutation_num)
        # 模拟开始:随机变异mutation_num位基因
        for i in range(mutation_num):
            obj_index = np.random.randint(0, len(p_cross))
            gene_index = np.random.randint(0, (96*8)+(56*1))
            p_cross[obj_index][gene_index] = np.random.uniform(-0.5, 0.5)
    p_mutation = p_cross
    # for i in range(len(p_mutation)):
    #     print(p_mutation[i].x)
    return p_mutation
